﻿using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;

namespace PathTracing
{
    [RequireComponent(typeof(Camera))]
    public class CameraMain : MonoBehaviour
    {
        private static class ConstantNames
        {
            public const string MainEnterPoint = "CSMain";
            public const string ResultTexture = "Result";
            public const string EnvironmentMap = "_EnvironmentMap";
            public const string RandomSeed = "_RandomSeed";
            public const string CameraToWorldMatrix = "_CameraToWorld";
            public const string CameraInverseProjection = "_CameraInverseProjection";
            //public const string PlanesBuffer = "_Planes";
            //public const string RandomBuffer = "_RandomBuffer";
            public const string SpheresBuffer = "_Spheres";
            public const string TrianglesBuffer = "_Triangles";
            public const string DirectionLightsBuffer = "_DirectionLights";
            public const string TracingIterateCount = "_RayTracingIterateCount";
            public const string PathTracingRayCount = "_PathTracingRayCount";
        }


        [SerializeField]
        private ComputeShader TracingShader = null;
        [Range(1,25)]
        [SerializeField]
        private int TracingIteratorCount = 5;
        [Range(1,1000)]
        [SerializeField]
        private int PathTracingRayCount = 100;
        [SerializeField]
        private Cubemap EnvironmentMap = null;

        private int TracingKernel = -1;
        private RenderTexture TracingResultTexture = null;

        //private ComputeBuffer RandomBuffer;
        //private ComputeBuffer PlanesBuffer;
        private ComputeBuffer SpheresBuffer;
        private ComputeBuffer TrianglesBuffer;
        private ComputeBuffer DirectionLightsBuffer;

        Camera CurrentCamera
        {
            get
            {
                return GetComponent<Camera>();
            }
        }

        // Start is called before the first frame update
        void Start()
        {
            if (TracingShader == null)
            {
                return;
            }
            TracingKernel = TracingShader.FindKernel(ConstantNames.MainEnterPoint);
            TracingResultTexture = new RenderTexture(Screen.width, Screen.height, 0);
            TracingResultTexture.enableRandomWrite = true;
            TracingResultTexture.Create();
            TracingShader.SetTexture(TracingKernel, ConstantNames.ResultTexture, TracingResultTexture);
            TracingShader.SetTexture(TracingKernel, ConstantNames.EnvironmentMap, EnvironmentMap);

            //RandomBuffer目前只需要设置一次
            //List<float> RandomData = new List<float>(Screen.width * Screen.height);
            //for(int i=0;i< Screen.width * Screen.height;i++)
            //{
            //    RandomData.Add(0);
            //}
            //CreateComputeBuffer(ref RandomBuffer, RandomData);
            //if (RandomBuffer != null)
            //{
            //    TracingShader.SetBuffer(TracingKernel, ConstantNames.RandomBuffer, RandomBuffer);
            //}
        }

        // Update is called once per frame
        void Update()
        {

        }

        private void OnDestroy()
        {
            //if(RandomBuffer != null)
            //{
            //    RandomBuffer.Dispose();
            //    RandomBuffer = null;
            //}
            //if(PlanesBuffer != null)
            //{
            //    PlanesBuffer.Dispose();
            //    PlanesBuffer = null;
            //}
            if(SpheresBuffer != null)
            {
                SpheresBuffer.Dispose();
                SpheresBuffer = null;
            }
            if(TrianglesBuffer != null)
            {
                TrianglesBuffer.Dispose();
                TrianglesBuffer = null;
            }
            if (DirectionLightsBuffer != null)
            {
                DirectionLightsBuffer.Dispose();
                DirectionLightsBuffer = null;
            }
        }

        private void OnRenderImage(RenderTexture source, RenderTexture destination)
        {
            if (TracingShader == null || TracingResultTexture == null)
            {
                Graphics.Blit(source, destination);
                return;
            }
            SetPerFrameTracingData();
            TracingShader.Dispatch(TracingKernel, Mathf.CeilToInt(Screen.width / 8), Mathf.CeilToInt(Screen.height / 8), 1);
            Graphics.Blit(TracingResultTexture, destination);
        }

        private static void CreateComputeBuffer<T> (ref ComputeBuffer buffer,List<T> data) where T : struct
        {
            int stride = Marshal.SizeOf<T>();
            if (buffer != null)
            {
                if (data.Count == 0 || buffer.count != data.Count || buffer.stride != stride)
                {
                    buffer.Release();
                    buffer = null;
                }
            }
            if (data.Count != 0 && buffer == null)
            {
                buffer = new ComputeBuffer(data.Count, stride);
            }

            if(buffer != null)
            {
                buffer.SetData(data);
            }
        }
        private void SetPerFrameTracingData()
        {
            TracingShader.SetFloat(ConstantNames.RandomSeed, Random.Range(-1000.0f, 1000f));

            TracingShader.SetMatrix(ConstantNames.CameraToWorldMatrix, CurrentCamera.cameraToWorldMatrix);
            TracingShader.SetMatrix(ConstantNames.CameraInverseProjection, CurrentCamera.projectionMatrix.inverse);

            TracingShader.SetInt(ConstantNames.TracingIterateCount, TracingIteratorCount);
            TracingShader.SetInt(ConstantNames.PathTracingRayCount, PathTracingRayCount);

            //CreateComputeBuffer(ref PlanesBuffer, TracingDataCollector.GetAllPlanes());
            //if(PlanesBuffer != null)
            //{
            //    TracingShader.SetBuffer(TracingKernel, ConstantNames.PlanesBuffer, PlanesBuffer);
            //}

            CreateComputeBuffer(ref SpheresBuffer, TracingDataCollector.GetAllSpheres());
            if(SpheresBuffer != null)
            {
                TracingShader.SetBuffer(TracingKernel, ConstantNames.SpheresBuffer, SpheresBuffer);
            }

            CreateComputeBuffer(ref TrianglesBuffer, TracingDataCollector.GetAllTriangles());
            if(TrianglesBuffer != null)
            {
                TracingShader.SetBuffer(TracingKernel, ConstantNames.TrianglesBuffer, TrianglesBuffer);
            }

            CreateComputeBuffer(ref DirectionLightsBuffer, TracingDataCollector.GetAllDirectionLights());
            if(DirectionLightsBuffer != null)
            {
                TracingShader.SetBuffer(TracingKernel, ConstantNames.DirectionLightsBuffer, DirectionLightsBuffer);
            }
        }
    }

}
